/*
  spr-sdk: Statistical Pattern Recognition SDK
  Copyright (C) 2010 Marco Ambu

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef _UTIL_MATRIX_H
#define	_UTIL_MATRIX_H

#include "util/Real.h"
#include <vector>
#include <memory>
#include <map>

namespace util {

class Matrix
{
public:
  class GenericType {};
  class SparseType {};
  class DiagonalType {};
  class SimmetricType {};

private:
  class MatrixImpl
  {
  public:
    virtual ~MatrixImpl() {}

    virtual MatrixImpl* clone() = 0;
    virtual Real& operator()(std::size_t row, std::size_t column) = 0;
    virtual Real operator()(std::size_t row, std::size_t column) const = 0;
  };

  class DiagonalImpl : public MatrixImpl
  {
  public:
    DiagonalImpl(std::size_t size);
    DiagonalImpl(std::size_t size, const std::vector<Real>& diagonalValues);

    virtual MatrixImpl* clone() { return new DiagonalImpl(*this); }

    virtual Real& operator()(std::size_t row, std::size_t column);
    virtual Real operator()(std::size_t row, std::size_t column) const;

  private:
    std::vector<Real> values_;
  };

  class SimmetricImpl : public MatrixImpl
  {
  public:
    SimmetricImpl(std::size_t size); // Square matrix!
    SimmetricImpl(std::size_t size, const std::vector<util::Real>& upperValues);

    virtual MatrixImpl* clone() { return new SimmetricImpl(*this); }

    virtual Real& operator()(std::size_t row, std::size_t column);
    virtual Real operator()(std::size_t row, std::size_t column) const;

  private:
    std::vector<Real> values_;
  };

  class GenericImpl : public MatrixImpl
  {
  public:
    GenericImpl(std::size_t rows, std::size_t cols);

    virtual MatrixImpl* clone() { return new GenericImpl(*this); }

    virtual Real& operator()(std::size_t row, std::size_t column);
    virtual Real operator()(std::size_t row, std::size_t column) const;

  private:
    std::size_t cols_;
    std::vector<Real> values_;
  };

  class SparseImpl : public MatrixImpl
  {
  public:
    SparseImpl(std::size_t rows, std::size_t cols);

    virtual MatrixImpl* clone() { return new SparseImpl(*this); }

    virtual Real& operator()(std::size_t row, std::size_t column);
    virtual Real operator()(std::size_t row, std::size_t column) const;

  private:
    std::size_t rows_;
    std::size_t cols_;
    std::map<std::pair<std::size_t, std::size_t>, Real> values_;
  };

public:
  Matrix(std::size_t rows, std::size_t columns, GenericType t = GenericType());
  Matrix(std::size_t rows, std::size_t columns, SparseType);

  Matrix(std::size_t size, DiagonalType);
  Matrix(std::size_t size, const std::vector<Real>& diagonalValues, DiagonalType);

  Matrix(std::size_t size, SimmetricType);
  Matrix(std::size_t size, const std::vector<Real>& upperValues, SimmetricType);

  Matrix(const Matrix& other);
  Matrix& operator=(const Matrix& other);

  inline std::size_t rows() const { return rows_; }
  inline std::size_t cols() const { return columns_; }

  bool isSparse() const;
  bool isDiagonal() const;
  bool isSimmetric() const;
  bool isGeneric() const;

  Real& operator()(std::size_t row, std::size_t column) { return (*impl_)(row, column); }
  Real operator()(std::size_t row, std::size_t column) const { return (*impl_)(row, column); }

  Real getValue(std::size_t row, std::size_t column) const {
    return static_cast<const MatrixImpl&>((*impl_))(row, column);
  }
  void setValue(std::size_t row, std::size_t column, Real value) {
      (*impl_)(row, column) = value;
  }

// TODO: come ottimizzare gli algoritmi per implementazione???
// TODO: fare una operation come per Pattern per eseguire le operazioni comuni
// magari due:
// 1) transform per operazioni di modifica della matrice (tipo transpose)
// 2) compute per operazioni che non modificano la matrice (tipo max)
public:
  Matrix transposed() const;
  Matrix& transpose();

  std::vector<Real> column(std::size_t c);

  Real max() const;
  Real min() const;

  Matrix& operator+=(const Matrix& matrix);
  Matrix& operator-=(const Matrix& matrix);

  Matrix& operator+=(Real value);
  Matrix& operator-=(Real value);
  Matrix& operator*=(Real value);
  Matrix& operator/=(Real value);
  Matrix& operator^=(Real value);

private:
  std::size_t rows_;
  std::size_t columns_;
  std::auto_ptr<MatrixImpl> impl_;

private:
  friend Matrix operator+(const Matrix& left, const Matrix& right);
  friend Matrix operator-(const Matrix& left, const Matrix& right);
  friend Matrix operator*(const Matrix& left, const Matrix& right);
};

Matrix operator+(const Matrix& left, const Matrix& right);
Matrix operator-(const Matrix& left, const Matrix& right);
Matrix operator*(const Matrix& left, const Matrix& right);

Matrix operator+(const Matrix& matrix, Real value);
Matrix operator-(const Matrix& matrix, Real value);
Matrix operator*(const Matrix& matrix, Real value);
Matrix operator/(const Matrix& matrix, Real value);
Matrix operator^(const Matrix& matrix, Real value);

} // namespace util

// import specialization for Formatter::toStream template method
#include "util/MatrixFormatter.h"

#endif	// _UTIL_MATRIX_H
